using System;
using System.Collections.Generic;

namespace AutoChess
{
    class EventRegInfo
    {
        public Delegate callback;
        public object owner;
        public bool deleted;

        public EventRegInfo(Delegate _callback, object _owner)
        {
            callback = _callback;
            owner = _owner;
            deleted = false;
        }
    }

    class ActorEventDispatcher
    {
        private Dictionary<int,List<EventRegInfo>> m_dictAllEventListener = new Dictionary<int, List<EventRegInfo>>();
        private List<int> m_processEvent = new List<int>();
        private List<int> m_delayDeleteEvent = new List<int>();

        public void DestroyAllEventListener()
        {
            var itr = m_dictAllEventListener.GetEnumerator();
            while (itr.MoveNext())
            {
                var kv = itr.Current;
                kv.Value.Clear();
            }
            
            m_processEvent.Clear();
            m_processEvent.Clear();
        }

        private void AddDelayDelete(int eventId)
        {
            if (!m_delayDeleteEvent.Contains(eventId))
            {
                m_delayDeleteEvent.Add(eventId);
                ExLog.LogErrorFormat("delay delete eventId[{0}]",eventId);
            }
        }

        private void CheckDelayDelete(int eventId)
        {
            if (m_delayDeleteEvent.Contains(eventId))
            {
                List<EventRegInfo> listListener;
                if (m_dictAllEventListener.TryGetValue(eventId, out listListener))
                {
                    for (int i = 0; i < listListener.Count; i++)
                    {
                        if (listListener[i].deleted)
                        {
                            ExLog.LogFormat(ExLogColor.Yellow,"remove delay delete eventId [{0}]",eventId);

                            listListener[i] = listListener[listListener.Count - 1];
                            listListener.RemoveAt(listListener.Count-1);
                            i--;
                        }
                    }
                }
            }

            m_delayDeleteEvent.Remove(eventId);
        }

        #region SendEvent

        public void SendEvent(ActorEventType eEventId)
        {
            int eventId = (int) eEventId;
            List<EventRegInfo> listListener;

#if UNITY_EDITOR
            int iEventCnt = m_processEvent.Count;
#endif
            
            if (m_dictAllEventListener.TryGetValue(eventId, out listListener))
            {
                m_processEvent.Add(eventId);

                var count = listListener.Count;
                for (int i = 0; i < count; i++)
                {
                    Action callBack = listListener[i].callback as Action;
                    if (callBack != null)
                    {
                        callBack();
                    }
                    else
                    {
                        BLogger.EditorFatal("Invalid event data type: {0}",eventId);
                    }
                }
            }
            
#if UNITY_EDITOR
            BLogger.Assert(iEventCnt == m_processEvent.Count);
            BLogger.Assert(eventId == m_processEvent[m_processEvent.Count - 1]);
#endif
            m_processEvent.RemoveAt(m_processEvent.Count -1);
            
            CheckDelayDelete(eventId);
        }

        public void SendEvent<T>(ActorEventType eEventId, T data)
        {
            int eventId = (int) eEventId;
            List<EventRegInfo> listListener;

#if UNITY_EDITOR
            int iEventCnt = m_processEvent.Count;
#endif
            
            if (m_dictAllEventListener.TryGetValue(eventId, out listListener))
            {
                m_processEvent.Add(eventId);

                var count = listListener.Count;
                for (int i = 0; i < count; i++)
                {
                    Action<T> callBack = listListener[i].callback as Action<T>;
                    if (callBack != null)
                    {
                        callBack(data);
                    }
                    else
                    {
                        BLogger.EditorFatal("Invalid event data type: {0}",eventId);
                    }
                }
            }
            
#if UNITY_EDITOR
            BLogger.Assert(iEventCnt == m_processEvent.Count);
            BLogger.Assert(eventId == m_processEvent[m_processEvent.Count - 1]);
#endif
            m_processEvent.RemoveAt(m_processEvent.Count -1);
            
            CheckDelayDelete(eventId);
        }
        
       
        public void SendEvent<T,U>(ActorEventType eEventId, T dataT,U dataU)
        {
            int eventId = (int) eEventId;
            List<EventRegInfo> listListener;

#if UNITY_EDITOR
            int iEventCnt = m_processEvent.Count;
#endif
            
            if (m_dictAllEventListener.TryGetValue(eventId, out listListener))
            {
                m_processEvent.Add(eventId);

                var count = listListener.Count;
                for (int i = 0; i < count; i++)
                {
                    Action<T,U> callBack = listListener[i].callback as Action<T,U>;
                    if (callBack != null)
                    {
                        callBack(dataT,dataU);
                    }
                    else
                    {
                        BLogger.EditorFatal("Invalid event data type: {0}",eventId);
                    }
                }
            }
            
#if UNITY_EDITOR
            BLogger.Assert(iEventCnt == m_processEvent.Count);
            BLogger.Assert(eventId == m_processEvent[m_processEvent.Count - 1]);
#endif
            m_processEvent.RemoveAt(m_processEvent.Count -1);
            
            CheckDelayDelete(eventId);
        }

        public void SendEvent<T,U,V>(ActorEventType eEventId, T dataT,U dataU,V dataV)
        {
            int eventId = (int) eEventId;
            List<EventRegInfo> listListener;

#if UNITY_EDITOR
            int iEventCnt = m_processEvent.Count;
#endif
            
            if (m_dictAllEventListener.TryGetValue(eventId, out listListener))
            {
                m_processEvent.Add(eventId);

                var count = listListener.Count;
                for (int i = 0; i < count; i++)
                {
                    Action<T,U,V> callBack = listListener[i].callback as Action<T,U,V>;
                    if (callBack != null)
                    {
                        callBack(dataT,dataU,dataV);
                    }
                    else
                    {
                        BLogger.EditorFatal("Invalid event data type: {0}",eventId);
                    }
                }
            }
            
#if UNITY_EDITOR
            BLogger.Assert(iEventCnt == m_processEvent.Count);
            BLogger.Assert(eventId == m_processEvent[m_processEvent.Count - 1]);
#endif
            m_processEvent.RemoveAt(m_processEvent.Count -1);
            
            CheckDelayDelete(eventId);
        }
        #endregion

        #region AddEvent

        public void AddEventListener(ActorEventType eventHashId, Action eventCallback, object owner)
        {
            AddEventListenerImp(eventHashId,eventCallback,owner);
        }

        public void AddEventListener<T>(ActorEventType eventHashId, Action<T> eventCallback, object owner)
        {
            AddEventListenerImp(eventHashId,eventCallback,owner);
        }
        
        public void AddEventListener<T,U>(ActorEventType eventHashId, Action<T,U> eventCallback, object owner)
        {
            AddEventListenerImp(eventHashId,eventCallback,owner);
        }
        
        public void AddEventListener<T,U,V>(ActorEventType eventHashId, Action<T,U,V> eventCallback, object owner)
        {
            AddEventListenerImp(eventHashId,eventCallback,owner);
        }
        
        private void AddEventListenerImp(ActorEventType eventHashId, Delegate listener, object owner)
        {
            List<EventRegInfo> listListener;
            if (!m_dictAllEventListener.TryGetValue((int) eventHashId, out listListener))
            {
                listListener = new List<EventRegInfo>();
                m_dictAllEventListener.Add(((int)eventHashId),listListener);
            }

            var existNode = listListener.Find((node) => { return node.callback == listener; });
            if (existNode != null)
            {
                if (existNode.deleted)
                {
                    existNode.deleted = false;
                    BLogger.EditorWarning("AddEvent hashId deleted,repeat add: {0}",eventHashId);
                    return;
                }
                BLogger.EditorFatal("AddEvent hashId repeated: {0}",eventHashId);
                return;
            }

            listListener.Add(new EventRegInfo(listener, owner));
        }

        #endregion

        #region RemoveEvent

        public void RemoveAllListenerByOwner(object owner)
        {
            var itr = m_dictAllEventListener.GetEnumerator();
            while (itr.MoveNext())
            {
                var kv = itr.Current;
                var list = kv.Value;

                int eventId = kv.Key;
                bool isProcessing = m_processEvent.Contains(eventId);
                bool delayDeleted = false;

                for (int i = 0; i < list.Count ; i++)
                {
                    var regInfo = list[i];
                    if (regInfo.owner == owner)
                    {
                        if (isProcessing)
                        {
                            regInfo.deleted = true;
                            delayDeleted = true;
                        }
                        else
                        {
                            list[i] = list[list.Count - 1];
                            list.RemoveAt(list.Count-1);
                            i--;
                        }
                    }
                }

                if (delayDeleted)
                {
                    AddDelayDelete(eventId);
                }
            }
        }

        public void RemoveEventListener(int eventHashId, Action eventCallBack)
        {
            RemoveEventListeneImp(eventHashId,eventCallBack);
        }

        public void RemoveEventListener<T>(int eventHashId, Action<T> eventCallBack)
        {
            RemoveEventListeneImp(eventHashId,eventCallBack);
        }
        
        public void RemoveEventListener<T,U>(int eventHashId, Action<T,U> eventCallBack)
        {
            RemoveEventListeneImp(eventHashId,eventCallBack);
        }
        
        public void RemoveEventListener<T,U,V>(int eventHashId, Action<T,U,V> eventCallBack)
        {
            RemoveEventListeneImp(eventHashId,eventCallBack);
        }
        
        private void RemoveEventListeneImp(int eventHashId, Delegate listener)
        {
            List<EventRegInfo> listListener;
            if (m_dictAllEventListener.TryGetValue(eventHashId, out listListener))
            {
                bool isProcessing = m_processEvent.Contains(eventHashId);
                if (!isProcessing)
                {
                    listListener.RemoveAll((node) => { return node.callback == listener; });
                }
                else
                {
                    int listenCnt = listListener.Count;
                    for (int i = 0; i < listenCnt; i++)
                    {
                        var node = listListener[i];
                        if (node.callback == listener)
                        {
                            node.deleted = true;
                            AddDelayDelete(eventHashId);
                            break;
                        }
                    }
                }
            }
        }

        #endregion
    }
}